/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts, AstTreeUtils } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { FileMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from '../../model/Fix';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoUnnecessaryConditionCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: "docs/no-unnecessary-condition-check.md",
    description: "Disallow conditionals where the type is always truthy or always falsy.",
};

interface Options {
    allowConstantLoopConditions?: boolean;
    allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
}

interface LocationInfo {
    fileName: string;
    line: number;
    startCol: number;
    endCol: number;
    start: number;
    end: number;
    nameStr: string;
}

export class NoUnnecessaryConditionCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private defaultOptions: Options = {
        allowConstantLoopConditions: false,
        allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
    };

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatcherCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        };
        return [fileMatcherCb];
    }

    public check = (arkFile: ArkFile) => {
        if (arkFile instanceof ArkFile) {
            const code = arkFile.getCode();
            if (!code) {
                return;
            }
            const filePath = arkFile.getFilePath();
            const asRoot = AstTreeUtils.getASTNode(arkFile.getName(), code);

            // 检查类型断言
            const locationInfos = this.checkCondition(asRoot, filePath);

            // 输出结果
            locationInfos.forEach(loc => {
                this.addIssueReportNodeFix(loc, filePath);
            });
        }
    }

    private checkCondition(sourceFile: ts.SourceFile, filePath: string): LocationInfo[] {
        const options: Options = this.rule && this.rule.option[0] ? this.rule.option[0] as Options : this.defaultOptions;
        const results: LocationInfo[] = [];
        const program = this.createProgram(filePath);
        const typeChecker = program.getTypeChecker();

        const visit = (node: ts.Node) => {
            let condition: ts.Expression | undefined;
            if (ts.isIfStatement(node)) {
                condition = node.expression;
                if (ts.isPropertyAccessExpression(condition)) {
                    return
                } else {
                    const conditionType = typeChecker.getTypeAtLocation(condition);
                    const conditionTypeStr = typeChecker.typeToString(conditionType);
                    const isIdentifierHasNullOrUndefined = this.isIdentifierHasNullOrUndefined2(node, condition.getText(), "any")
                    if ((conditionTypeStr === "any" && !isIdentifierHasNullOrUndefined) || this.isAlwaysTruthyOrFalsy(conditionType, typeChecker)) {
                        const { line, character } = sourceFile.getLineAndCharacterOfPosition(condition.getStart());
                        const assertionName = condition.getText();
                        const start = condition.getStart();
                        const end = condition.getEnd();
                        const endCharacter = character + assertionName.length;
                        results.push({
                            fileName: sourceFile.fileName,
                            line: line + 1,
                            startCol: character + 1,
                            endCol: endCharacter + 1,
                            start: start,
                            end: end,
                            nameStr: assertionName
                        });
                    }
                }
                return
            } else if (ts.isWhileStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.expression;
            } else if (ts.isDoStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.expression;
            } else if (ts.isForStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.condition;
            } else if (ts.isConditionalExpression(node)) {
                condition = node.condition;
            } else if (ts.isOptionalChain(node)) {
                condition = node.expression;
            } else if (ts.isCallExpression(node)) {
                const expression = node.expression;
                const args = node.arguments;
                // 检测是否是 filter 方法调用
                const arrayPredicate = new Set(['filter', 'find', 'some', 'every',]);
                if (ts.isPropertyAccessExpression(expression) && arrayPredicate.has(expression.name.text)) {
                    const callback = args[0];
                    if (callback && ts.isArrowFunction(callback) && ts.isIdentifier(callback.body)) {
                        condition = callback.body
                    }
                }
            } else if (ts.isBinaryExpression(node)) {
                if (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken
                    || node.operatorToken.kind === ts.SyntaxKind.BarBarEqualsToken
                    || node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken
                    || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {

                    const leftType = typeChecker.getTypeAtLocation(node.left);
                    // 检查左侧类型是否可能为 null 或 undefined
                    const isLeftAlwaysNullOrUndefined = this.isAlwaysNull(leftType, typeChecker) || this.isAlwaysUndefined(leftType, typeChecker);
                    if (!isLeftAlwaysNullOrUndefined || (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken && isLeftAlwaysNullOrUndefined)) {
                        const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.left.getStart());
                        const assertionName = node.left.getText();
                        const start = node.left.getStart();
                        const end = node.left.getEnd();
                        const endCharacter = character + assertionName.length;
                        results.push({
                            fileName: sourceFile.fileName,
                            line: line + 1,
                            startCol: character + 1,
                            endCol: endCharacter + 1,
                            start: start,
                            end: end,
                            nameStr: assertionName
                        });
                    }
                } else {
                    condition = node;
                }
            }

            if (condition) {
                if (this.checkConditionType(sourceFile, node, condition, typeChecker)) {
                    let start: number = 0;
                    let end: number = 0;
                    let position: ts.LineAndCharacter | undefined;
                    let assertionName: string = '';
                    if (ts.isBinaryExpression(condition)) {
                        const rightType = typeChecker.getTypeAtLocation(condition.right);
                        const isRightAlwaysFalsy = this.isAlwaysTruthyOrFalsy(rightType, typeChecker);
                        if (isRightAlwaysFalsy) {
                            position = sourceFile.getLineAndCharacterOfPosition(condition.right.getStart());
                            assertionName = condition.right.getText();
                            start = condition.right.getStart();
                            end = condition.right.getEnd();
                        } else {
                            position = sourceFile.getLineAndCharacterOfPosition(condition.left.getStart());
                            assertionName = condition.left.getText();
                            start = condition.left.getStart();
                            end = condition.left.getEnd();

                        }
                    } else if (ts.isOptionalChain(node)) {
                        if (ts.isPropertyAccessExpression(node)
                            || ts.isElementAccessExpression(node)
                            || ts.isCallExpression(node)) {
                            const questionDotToken = node.questionDotToken;
                            if (questionDotToken) {
                                start = questionDotToken.getStart();
                                end = questionDotToken.getEnd();
                                position = sourceFile.getLineAndCharacterOfPosition(questionDotToken.getStart());
                                assertionName = questionDotToken.getText();
                            }
                        }
                    } else {
                        start = condition.getStart();
                        end = condition.getEnd();
                        position = sourceFile.getLineAndCharacterOfPosition(condition.getStart());
                        assertionName = condition.getText();
                    }
                    if (position) {
                        const endCharacter = position.character + assertionName.length;
                        results.push({
                            fileName: sourceFile.fileName,
                            line: position.line + 1,
                            startCol: position.character + 1,
                            endCol: endCharacter + 1,
                            start: start,
                            end: end,
                            nameStr: assertionName
                        });
                    }
                }
            }

            ts.forEachChild(node, visit);
        };

        visit(sourceFile);
        return results;
    }

    private checkConditionType(sourceFile: ts.SourceFile, node: ts.Node, condition: ts.Expression, typeChecker: ts.TypeChecker): boolean {
        const conditionType = typeChecker.getTypeAtLocation(condition);
        if (conditionType.flags & ts.TypeFlags.BooleanLiteral) {
            return this.isAlwaysTruthyOrFalsy(conditionType, typeChecker);
        } else if (ts.isBinaryExpression(condition)) {
            const leftType = typeChecker.getTypeAtLocation(condition.left);
            const rightType = typeChecker.getTypeAtLocation(condition.right);

            if (condition.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
                // && 操作符
                const isLeftAlwaysFalsy = this.isAlwaysTruthyOrFalsy(leftType, typeChecker);
                const isRightAlwaysFalsy = this.isAlwaysTruthyOrFalsy(rightType, typeChecker);

                if (isLeftAlwaysFalsy || isRightAlwaysFalsy) {
                    return true;
                }
            } else if (condition.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
                // || 操作符
                const isLeftAlwaysTruthy = this.isAlwaysTruthyOrFalsy(leftType, typeChecker);
                const isRightAlwaysTruthy = this.isAlwaysTruthyOrFalsy(rightType, typeChecker);

                if (isLeftAlwaysTruthy || isRightAlwaysTruthy) {
                    return true;
                }
            } else {
                // 检查是否比较 undefined 或 null
                const isUndefinedComparison = this.isAlwaysUndefined(leftType, typeChecker) || this.isAlwaysUndefined(rightType, typeChecker);
                const isNullComparison = this.isAlwaysNull(leftType, typeChecker) || this.isAlwaysNull(rightType, typeChecker);

                // 检查另一侧是否为明确的类型（如 string、number 等）
                const isExplicitType = this.isAlwaysAny(leftType, typeChecker) || this.isAlwaysAny(rightType, typeChecker);

                if ((isUndefinedComparison || isNullComparison) && isExplicitType) {
                    return true;
                }
            }

        } else {
            const conditionTypeStr = typeChecker.typeToString(conditionType);
            // 检查类型是否为函数类型
            const isFunctionType = conditionType.flags & ts.TypeFlags.Object;
            if (isFunctionType) {
                return !this.isIdentifierHasNullOrUndefined(sourceFile, condition.getText(), "object")
            }

            if (conditionTypeStr === "number" || conditionTypeStr === "string" || conditionTypeStr === "boolean") {
                return true;
            }
            if (conditionTypeStr === "any") {
                const isIdentifierHasNullOrUndefined = this.isIdentifierHasNullOrUndefined1(node, condition.getText())
                return !isIdentifierHasNullOrUndefined
            }
        }
        return false;
    }

    private isIdentifierHasNullOrUndefined(sourceFile: ts.SourceFile, identifier: string, type: string): boolean {
        for (let stmtNode of sourceFile?.statements ?? []) {
            if (!ts.isVariableStatement(stmtNode)) {
                continue;
            }
            let variableList = stmtNode.declarationList;
            if (variableList && variableList.declarations) {
                for (let declaration of variableList.declarations) {
                    if (!ts.isVariableDeclaration(declaration)) {
                        continue;
                    }
                    let nameIdentifier = declaration.name;
                    if (ts.isIdentifier(nameIdentifier) && nameIdentifier.escapedText === identifier) {
                        if (declaration.initializer?.getText().includes("null") || declaration.initializer?.getText().includes("undefined")) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private isIdentifierHasNullOrUndefined1(node: ts.Node, identifier: string): boolean {
        // 遍历父节点语句
        let parentNode = node.parent
        while (parentNode) {
            // 递归检查每个语句节点
            if (this.checkNode1(parentNode, identifier)) {
                return true;
            }
            parentNode = parentNode.parent
        }
        return false;
    }
    private checkNode1(node: ts.Node, identifier: string): boolean {
        // 如果当前节点是变量声明
        if (ts.isVariableStatement(node)) {
            for (const declaration of node.declarationList.declarations) {
                // 检查变量名是否匹配
                if (ts.isIdentifier(declaration.name) && declaration.name.text === identifier) {
                    // 检查初始化值是否为 null 或 undefined
                    if (declaration.initializer) {
                        if (this.isNullOrUndefined(declaration.initializer)) {
                            return true;
                        }
                    }
                }
            }
        }
        // 如果当前节点是函数声明或方法声明
        if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
            const parameters = node.parameters;
            for (let i = 0; i < parameters.length; i++) {
                const parameter = parameters[i];
                if (parameter.name.getText() === identifier) {

                    if (parameter.type) {
                        const parameterType = parameter.type?.getText()?.toString();
                        if (parameterType.includes("null") || parameterType.includes("undefined")) {
                            return true;
                        }
                    }
                }
            }
        }
        // 如果当前节点是表达式语句
        else if (ts.isExpressionStatement(node)) {
            if (this.isNullOrUndefined(node.expression)) {
                return true;
            }
        }
        // 如果当前节点是赋值表达式
        else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
            // 检查赋值的左侧是否为目标标识符
            if (ts.isIdentifier(node.left) && node.left.text === identifier) {
                // 检查赋值的右侧是否为 null 或 undefined
                if (this.isNullOrUndefined(node.right)) {
                    return true;
                }
            }
        }
        // 递归检查子节点
        for (const child of node.getChildren()) {
            this.checkNode1(child, identifier)
        }
        return false;
    }

    private isIdentifierHasNullOrUndefined2(node: ts.Node, identifier: string, type: string): boolean {
        // 遍历父节点语句
        let parentNode = node.parent
        while (parentNode) {
            // 递归检查每个语句节点
            if (this.checkNode2(parentNode, identifier, type)) {
                return true;
            }
            parentNode = parentNode.parent
        }
        return false;
    }

    private checkNode2(node: ts.Node, identifier: string, type: string): boolean {
        // 如果当前节点是变量声明
        if (ts.isVariableStatement(node)) {
            for (const declaration of node.declarationList.declarations) {
                // 检查变量名是否匹配
                if (ts.isIdentifier(declaration.name) && declaration.name.text === identifier) {
                    // 检查初始化值是否为 null 或 undefined
                    if (declaration.initializer) {
                        if (this.isNullOrUndefined(declaration.initializer)) {
                            return true;
                        }
                    }
                }
            }
        }
        // 如果当前节点是函数声明或方法声明
        if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
            const parameters = node.parameters;
            for (let i = 0; i < parameters.length; i++) {
                const parameter = parameters[i];
                if (parameter.name.getText() === identifier) {

                    if (parameter.type) {
                        const parameterType = parameter.type?.getText()?.toString();
                        if ((parameterType.includes("string") && !parameterType.includes("string[]")) || parameterType.includes("null") || parameterType.includes("undefined")) {
                            return true;
                        }
                    }
                }
            }
        }
        // 如果当前节点是表达式语句
        else if (ts.isExpressionStatement(node)) {
            if (this.isNullOrUndefined(node.expression)) {
                return true;
            }
        }
        // 如果当前节点是赋值表达式
        else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
            // 检查赋值的左侧是否为目标标识符
            if (ts.isIdentifier(node.left) && node.left.text === identifier) {
                // 检查赋值的右侧是否为 null 或 undefined
                if (this.isNullOrUndefined(node.right)) {
                    return true;
                }
            }
        }
        // 递归检查子节点
        for (const child of node.getChildren()) {
            this.checkNode2(child, identifier, type)
        }
        return false;
    }

    private isAlwaysTruthyOrFalsy(type: ts.Type, typeChecker: ts.TypeChecker): boolean {
        // 检查类型是否总是为真或为假
        const typeString = typeChecker.typeToString(type);
        const isAlwaysTruthy = typeString === "true";
        const isAlwaysFalsy = typeString === "false";
        return isAlwaysTruthy || isAlwaysFalsy;
    }

    private isNullOrUndefined(node: ts.Node): boolean {
        // 检查节点是否为 null 或 undefined
        return (ts.isIdentifier(node) && (node.text.includes("null") || node.text.includes("undefined")));
    }

    private isAlwaysNull(type: ts.Type, typeChecker: ts.TypeChecker): boolean {
        const typeString = typeChecker.typeToString(type);
        const isAlwaysNull = typeString === "null";
        return isAlwaysNull;
    }
    private isAlwaysUndefined(type: ts.Type, typeChecker: ts.TypeChecker): boolean {
        const typeString = typeChecker.typeToString(type);
        const isAlwaysUndefined = typeString === "undefined";
        return isAlwaysUndefined;
    }
    private isAlwaysAny(type: ts.Type, typeChecker: ts.TypeChecker): boolean {
        const typeString = typeChecker.typeToString(type);
        const isAlwaysAny = typeString === "any";
        return isAlwaysAny;
    }

    private createProgram(filePath: string): ts.Program {
        return ts.createProgram([filePath], {});
    }

    // 创建修复对象 
    private ruleFix(loc: LocationInfo): RuleFix {
        const [start, end] = this.getFixRange(loc);
        return { range: [start, end], text: '.' };
    }

    // 获取起始位置和结束位置
    private getFixRange(loc: LocationInfo): [number, number] {
        const startPosition = loc.start;
        const endPosition = loc.end;
        return [startPosition, endPosition];
    }

    private addIssueReportNodeFix(loc: LocationInfo, filePath: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        let defect = new Defects(loc.line, loc.startCol, loc.endCol, this.metaData.description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        if (loc.nameStr === "?.") {
            let fix: RuleFix = this.ruleFix(loc);
            this.issues.push(new IssueReport(defect, fix));
        } else {
            this.issues.push(new IssueReport(defect, undefined));
        }
        RuleListUtil.push(defect);
    }
}